
#include "hscuda_rasterdata.h"
#include <cuda_runtime.h>
#include "hs_logger.h"

template<typename DataType>
hscuda::raster::RasterData<DataType>::RasterData()
    : m_p_rawdata(nullptr), m_view(), m_alloc_size(0)
{ }

template<typename DataType>
hscuda::raster::RasterData<DataType>::RasterData(int size_x, int size_y)
    : m_p_rawdata(nullptr), m_view(), m_alloc_size(0)
{
    init(size_x, size_y);
}

template<typename DataType>
hscuda::raster::RasterData<DataType>::~RasterData()
{
    release();
}

template<typename DataType>
void hscuda::raster::RasterData<DataType>::init(int size_x, int size_y)
{
    release();

    if (size_x <= 0 || size_y <= 0) {
        LOG_FATAL("hscuda::raster::RasterData::init Fatal Error: size_x(" $ size_x $ ") <= 0 or size_y(" $ size_y $ ") <= 0\n");
    }
    m_alloc_size = size_x * size_y;

    cudaError_t err;
    err = cudaMalloc(&m_p_rawdata, m_alloc_size * sizeof(DataType));
    if (err != cudaSuccess) {
        LOG_FATAL("hscuda::raster::RasterData::init Fatal Error: cudaMalloc failed, EID = " $ (int)err $ ", Error Message: " $ cudaGetErrorString(err) $ std::endl);
    }

    m_view.init(size_x, size_y, m_p_rawdata);
}

template<typename DataType>
void hscuda::raster::RasterData<DataType>::release()
{
    if (m_p_rawdata) {
        cudaError_t err = cudaFree(m_p_rawdata);
        if (err != cudaSuccess) {
            LOG_FATAL("hscuda::raster::RasterData::release Fatal Error: cudaFree failed, EID = " $ (int)err $ ", Error Message: " $ cudaGetErrorString(err) $ std::endl);
        }
        m_p_rawdata = nullptr;
        m_alloc_size = 0;
    }
    m_view.clear();
}

template<typename DataType>
hscuda::raster::RasterData<DataType>::RasterData(RasterData & obj)
    : m_p_rawdata(nullptr), m_view(), m_alloc_size(0)
{
    m_p_rawdata = obj.m_p_rawdata;
    m_alloc_size = obj.m_alloc_size;
    m_view = obj.m_view;

    obj.m_p_rawdata = nullptr;
    obj.m_alloc_size = 0;
    obj.m_view.clear();
}

template<typename DataType>
hscuda::raster::RasterData<DataType>::RasterData(RasterData && obj)
    : m_p_rawdata(nullptr), m_view(), m_alloc_size(0)
{
    m_p_rawdata = obj.m_p_rawdata;
    m_alloc_size = obj.m_alloc_size;
    m_view = obj.m_view;

    obj.m_p_rawdata = nullptr;
    obj.m_alloc_size = 0;
    obj.m_view.clear();
}

template<typename DataType>
hscuda::raster::RasterData<DataType> & hscuda::raster::RasterData<DataType>::operator=(RasterData & obj)
{
    if (&obj == this) {
        return *this;
    }

    release();
    m_p_rawdata = obj.m_p_rawdata;
    m_alloc_size = obj.m_alloc_size;
    m_view = obj.m_view;

    obj.m_p_rawdata = nullptr;
    obj.m_alloc_size = 0;
    obj.m_view.clear();

    return *this;
}

template<typename DataType>
hscuda::raster::RasterData<DataType> & hscuda::raster::RasterData<DataType>::operator=(RasterData && obj)
{
    if (&obj == this) {
        return *this;
    }

    release();
    m_p_rawdata = obj.m_p_rawdata;
    m_alloc_size = obj.m_alloc_size;
    m_view = obj.m_view;

    obj.m_p_rawdata = nullptr;
    obj.m_alloc_size = 0;
    obj.m_view.clear();

    return *this;
}


template<typename DataType>
void hscuda::raster::RasterData<DataType>::copyFrom(const RasterData & obj)
{
    release();

    init(obj.m_view.sizeX(), obj.m_view.sizeY());
    size_t num_bytes = sizeof(DataType) * obj.m_view.numel();
    cudaError_t err = cudaMemcpy(m_view.pData(), obj.m_view.pData(), num_bytes, cudaMemcpyDeviceToDevice);
    if (err != cudaSuccess) {
        LOG_FATAL("hscuda::raster::RasterData::copyFrom Fatal Error: cudaMemcpy failed, EID = " $ (int)err $ ", Error Message: " $ cudaGetErrorString(err) $ std::endl);
    }

    if (obj.m_view.hasNodata()) {
        m_view.setNodata(obj.m_view.nodata());
    }
    else {
        m_view.setDefaultNodata();
    }
}

template<typename DataType>
bool hscuda::raster::RasterData<DataType>::toHost(hs::raster::RasterData<DataType>& obj)
{
    obj.init(m_view.sizeX(), m_view.sizeY(), m_view.isRowMajor());
    cudaError_t err = cudaMemcpy(obj.view().pData(), m_view.pData(), sizeof(DataType) * m_view.numel(), cudaMemcpyDeviceToHost);
    if (err != cudaSuccess) {
        LOG_ERROR("hscuda::raster::RasterData::toHost Error: cudaMemcpy failed, EID=" $ err $ ", Error Message: " $ cudaGetErrorString(err) $ std::endl);
        return false;
    }
    return true;
}

template<typename DataType>
bool hscuda::raster::host2device(const hs::raster::RasterView<DataType>& host_view, hscuda::raster::RasterData<DataType>& raster)
{
    if (!host_view.isRowMajor()) {
        LOG_WARN("hscuda::raster::host2device warning: host_view.isRowMajor() == false\n");
    }
    
    raster.init(host_view.sizeX(), host_view.sizeY());

    cudaError_t err = cudaSuccess;
    err = cudaMemcpy(raster.view().pData(), host_view.pData(), sizeof(DataType) * host_view.numel(), cudaMemcpyHostToDevice);
    if (err != cudaSuccess) {
        LOG_FATAL("hscuda::raster::host2device Error: cudaMemcpy failed, EID=" $ err $ ", Error Message: " $ cudaGetErrorString(err) $ std::endl);
        return false;
    }

    return true;
}

template<typename DataType>
bool hscuda::raster::device2host(const hscuda::raster::RasterView<DataType>& device_view, hs::raster::RasterData<DataType>& raster)
{
    raster.init(device_view.sizeX(), device_view.sizeY(), device_view.isRowMajor());
    cudaError_t err = cudaMemcpy(raster.view().pData(), device_view.pData(), sizeof(DataType) * device_view.numel(), cudaMemcpyDeviceToHost);
    if (err != cudaSuccess) {
        LOG_ERROR("hscuda::raster::device2host Error: cudaMemcpy failed, EID=" $ err $ ", Error Message: " $ cudaGetErrorString(err) $ std::endl);
        return false;
    }
    return true;
}

/* 显示实例化 */
template class hscuda::raster::RasterData<float>;
template class hscuda::raster::RasterData<double>;

template bool hscuda::raster::host2device<double>(const hs::raster::RasterView<double>& host_view, hscuda::raster::RasterData<double>& raster);
template bool hscuda::raster::host2device<float>(const hs::raster::RasterView<float>& host_view, hscuda::raster::RasterData<float>& raster);

template bool hscuda::raster::device2host<double>(const hscuda::raster::RasterView<double>& device_view, hs::raster::RasterData<double>& raster);
template bool hscuda::raster::device2host<float>(const hscuda::raster::RasterView<float>& device_view, hs::raster::RasterData<float>& raster);
